package com.huxi.lang.object.support;

import static com.huxi.lang.utils.BuilderUtils.findAnnotation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;

import com.huxi.lang.annotation.EnumDescribe;
import com.huxi.lang.exception.ValueParseException;

/**
 * 枚举类型构造器
 * 
 * @author jian.wu
 * @since 1.0
 * @version 1.0
 */
public class EnumBuilder<T extends Enum<T>> {

	public static final List<Class<?>> ENUM_VALUE_CLASSES = Collections
			.unmodifiableList(Arrays.asList(Integer.TYPE, Short.TYPE, Byte.TYPE));
	private static final String ILLEGAL_CONSTANT_MSG = "illegal enum constant ~ %s";
	private static final String ILLEGAL_ORDINAL_MSG = "illegal enum ordinal ~ %d";
	private static final String METHOD_FAILED_MSG = "@EnumValue.%() method - %s.%s():%s [%s]";
	private static final String INVOKE_FAILED_MSG = "@EnumValue.%s() invoke [%s] - %s.%s() [%s] ~ %s";

	private final Class<T> enumClass;
	private final T[] enumsList;
	private final int[] valuesList;
	private final String[] namesList;
	private final boolean custom;

	private EnumBuilder(Class<T> enumClass, T[] enumsList, int[] valuesList, String[] namesList) {
		this.enumClass = Objects.requireNonNull(enumClass);
		this.enumsList = Objects.requireNonNull(enumsList);
		this.valuesList = valuesList;
		this.namesList = namesList;
		this.custom = valuesList != null || namesList != null;
	}

	public static <T extends Enum<T>> EnumBuilder<T> of(Class<T> enumClass) {
		return of(enumClass, false, null);
	}

	public static <T extends Enum<T>> EnumBuilder<T> of(Class<T> enumClass, boolean custom,
			BiConsumer<Class<?>, Exception> consumer) {
		Objects.requireNonNull(enumClass);
		T[] enumsList = enumClass.getEnumConstants();
		if (!custom || enumsList.length == 0) {
			return new EnumBuilder<T>(enumClass, enumsList, null, null);
		}
		LinkedHashSet<Number> valuesSet = null;
		Exception valuesThrowable = null;
		LinkedHashSet<String> namesSet = null;
		Exception namesThrowable = null;

		EnumDescribe an = findAnnotation(enumClass, EnumDescribe.class, true);
		if (an != null) {
			String valueMethodName = an.ordinal().trim();
			if (!(valueMethodName.length() == 0 || valueMethodName.equals("ordinal"))) {
				Number v = null;
				T n = null;
				try {
					Method m = enumClass.getMethod(valueMethodName);
					Class<?> r = m.getReturnType();
					if (!ENUM_VALUE_CLASSES.contains(r)) {
						throw new IllegalStateException(
								String.format(METHOD_FAILED_MSG, "ordinal", enumClass, valueMethodName, r, "illegal"));
					}
					valuesSet = new LinkedHashSet<>();
					for (int i = 0; i < enumsList.length; i++) {
						n = enumsList[i];
						v = (Number) m.invoke(n);
						if (v == null) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "ordinal", n.name(),
									enumClass, valueMethodName, "empty", v));
						}
						if (valuesSet.contains(v)) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "ordinal", n.name(),
									enumClass, valueMethodName, "repeat", v));
						}
						valuesSet.add(v);
					}
				} catch (NoSuchMethodException e) {
					valuesThrowable = new IllegalStateException(
							String.format(METHOD_FAILED_MSG, "ordinal", enumClass, valueMethodName, "", "not exists"));
				} catch (IllegalStateException e) {
					valuesThrowable = e;
				} catch (Exception e) {
					if (n == null) {
						valuesThrowable = new IllegalStateException(String.format(METHOD_FAILED_MSG, "ordinal",
								enumClass, valueMethodName, "", "find failed"));
					} else {
						valuesThrowable = new IllegalStateException(String.format(INVOKE_FAILED_MSG, "ordinal",
								n.name(), enumClass, valueMethodName, "failed", e.getMessage()));
					}
				}
				if (valuesThrowable != null && consumer != null) {
					consumer.accept(enumClass, valuesThrowable);
				}
			}
			String nameMethodName = an.display().trim();
			if (!(nameMethodName.length() == 0 || nameMethodName.equals("name"))) {
				String s = null;
				T n = null;
				try {
					Method m = enumClass.getMethod(nameMethodName);
					Class<?> r = m.getReturnType();
					if (r != String.class) {
						throw new IllegalStateException(
								String.format(METHOD_FAILED_MSG, "name", enumClass, nameMethodName, r, "illegal"));
					}
					namesSet = new LinkedHashSet<>();
					for (int i = 0; i < enumsList.length; i++) {
						n = enumsList[i];
						s = (String) m.invoke(n);
						if (s == null) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "name", n.name(),
									enumClass, nameMethodName, "empty", s));
						}
						s = s.trim();
						if (s.length() == 0) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "name", n.name(),
									enumClass, nameMethodName, "empty", s));
						}
						if (Character.isDigit(s.charAt(0))) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "name", n.name(),
									enumClass, nameMethodName, "digit start", s));
						}

						if (namesSet.contains(s)) {
							throw new IllegalStateException(String.format(INVOKE_FAILED_MSG, "name", n.name(),
									enumClass, nameMethodName, "repeat", s));
						}
						namesSet.add(s);
					}
				} catch (NoSuchMethodException e) {
					namesThrowable = new IllegalStateException(
							String.format(METHOD_FAILED_MSG, "name", enumClass, nameMethodName, "", "not exists"));
				} catch (IllegalStateException e) {
					namesThrowable = e;
				} catch (Exception e) {
					if (n == null) {
						namesThrowable = new IllegalStateException(
								String.format(METHOD_FAILED_MSG, "name", enumClass, nameMethodName, "", "find failed"));
					} else {
						namesThrowable = new IllegalStateException(String.format(INVOKE_FAILED_MSG, "name", n.name(),
								enumClass, nameMethodName, "failed", e.getMessage()));
					}
				}
				if (namesThrowable != null && consumer != null) {
					consumer.accept(enumClass, namesThrowable);
				}
			}
		}

		int[] valuesList = null;
		if (valuesThrowable == null) {
			valuesList = new int[enumsList.length];
			int i = 0;
			for (Number n : valuesSet) {
				valuesList[i++] = n.intValue();
			}
		}

		String[] namesList = null;
		if (namesThrowable == null) {
			namesList = new String[enumsList.length];
			int i = 0;
			for (String s : namesSet) {
				namesList[i++] = s;
			}
		}

		return new EnumBuilder<T>(enumClass, enumsList, valuesList, namesList);
	}

	public Class<T> getEnumClass() {
		return enumClass;
	}

	public boolean isIllegal() {
		return enumsList.length == 0;
	}

	public boolean isCustom() {
		return custom;
	}

	public Object getEnumValue(T value) {
		return valuesList == null ? value.ordinal() : valuesList[value.ordinal()];
	}

	public String getEnumName(T value) {
		return namesList == null ? value.name() : namesList[value.ordinal()];
	}

	public boolean isInstance(T value) {
		return enumClass.isInstance(value);
	}

	public T valueOf(String name) throws ValueParseException {
		try {
			return Enum.valueOf(enumClass, name);
		} catch (IllegalArgumentException e) {
		}
		throw new ValueParseException(enumClass, name, String.format(ILLEGAL_CONSTANT_MSG, name));
	}

	public T valueOf(String name, boolean custom) throws ValueParseException {
		if (custom && namesList != null) {
			for (int i = 0; i < namesList.length; i++) {
				if (name.equals(namesList[i])) {
					return enumsList[i];
				}
			}
		} else {
			try {
				return Enum.valueOf(enumClass, name);
			} catch (IllegalArgumentException e) {
			}
		}
		throw new ValueParseException(enumClass, name, String.format(ILLEGAL_CONSTANT_MSG, name));
	}

	public T valueOf(int ordinal) throws ValueParseException {
		try {
			return enumsList[ordinal];
		} catch (Exception e) {
		}
		throw new ValueParseException(enumClass, String.valueOf(ordinal), String.format(ILLEGAL_ORDINAL_MSG, ordinal));
	}

	public T valueOf(int ordinal, boolean custom) throws ValueParseException {
		if (custom && valuesList != null) {
			for (int i = 0; i < valuesList.length; i++) {
				if (ordinal == valuesList[i]) {
					return enumsList[i];
				}
			}
		} else {
			try {
				return enumsList[ordinal];
			} catch (Exception e) {
			}
		}
		throw new ValueParseException(enumClass, String.valueOf(ordinal), String.format(ILLEGAL_ORDINAL_MSG, ordinal));
	}

	public T parse(String value) {
		boolean o = Character.isDigit(value.charAt(0));
		if (o) {
			try {
				return valueOf(Integer.parseInt(value, 10));
			} catch (NumberFormatException e) {
			}
		} else {
			return valueOf(value);
		}
		throw new ValueParseException(enumClass, value, String.format(ILLEGAL_CONSTANT_MSG, value));
	}

	public T parse(String value, boolean custom) throws ValueParseException {
		boolean o = Character.isDigit(value.charAt(0));
		if (o) {
			try {
				return valueOf(Integer.parseInt(value, 10), custom);
			} catch (NumberFormatException e) {
			}
		} else {
			return valueOf(value, custom);
		}
		throw new ValueParseException(enumClass, value, String.format(ILLEGAL_CONSTANT_MSG, value));
	}

	public T parseIgnoreCase(String value) {
		boolean o = Character.isDigit(value.charAt(0));
		if (o) {
			try {
				return valueOf(Integer.parseInt(value, 10));
			} catch (NumberFormatException e) {
			}
		} else {
			T t = null;
			for (int i = 0; i < enumsList.length; i++) {
				t = enumsList[i];
				if (value.equalsIgnoreCase(t.name())) {
					return t;
				}
			}
		}
		throw new ValueParseException(enumClass, value, String.format(ILLEGAL_CONSTANT_MSG, value));
	}

	public T parseIgnoreCase(String value, boolean custom) throws ValueParseException {
		boolean o = Character.isDigit(value.charAt(0));
		if (o) {
			try {
				return valueOf(Integer.parseInt(value, 10), custom);
			} catch (NumberFormatException e) {
			}
		} else {
			if (custom && namesList != null) {
				for (int i = 0; i < namesList.length; i++) {
					if (value.equalsIgnoreCase(namesList[i])) {
						return enumsList[i];
					}
				}
			} else {
				T t = null;
				for (int i = 0; i < enumsList.length; i++) {
					t = enumsList[i];
					if (value.equalsIgnoreCase(t.name())) {
						return t;
					}
				}
			}
		}
		throw new ValueParseException(enumClass, value, String.format(ILLEGAL_CONSTANT_MSG, value));
	}

	public T cast(Object value, T defaultValue, BiConsumer<Object, Exception> consumer) {
		if (value == null) {
			return defaultValue;
		} else if (enumClass.isInstance(value)) {
			return enumClass.cast(value);
		}

		if (value instanceof CharSequence) {
			return parse(value.toString());
		} else if (value instanceof Number) {
			return valueOf(((Number) value).intValue());
		}
		return defaultValue;
	}
}
